package com.miyake.dsp.osa.lab.ui;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Graphics2D;
import java.awt.Point;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.miyake.dsp.osa.lab.Connector;
import com.miyake.dsp.osa.lab.LabContainer;

public abstract class WirePainter {
	public Component getConnectorComponent() {
		return connectorComponent;
	}

	public void setConnectorComponent(Component connectorComponent) {
		this.connectorComponent = connectorComponent;
	}

	public LabContainer getLastConnectedContainer() {
		return lastConnectedContainer;
	}

	public void setLastConnectedContainer(LabContainer lastConnectedContainer) {
		this.lastConnectedContainer = lastConnectedContainer;
	}

	public String getLastConnectedConnectorKey() {
		return lastConnectedConnectorKey;
	}

	public void setLastConnectedConnectorKey(String lastConnectedConnectorKey) {
		this.lastConnectedConnectorKey = lastConnectedConnectorKey;
	}

	private boolean isConnectorDragging() {
		return connectorComponent != null;
	}
	

	public Point getDragEnd() {
		return dragEnd;
	}

	public void setDragEnd(Point dragEnd) {
		this.dragEnd = dragEnd;
	}

	public Point getDragStart() {
		return dragStart;
	}

	public void setDragStart(Point dragStart) {
		this.dragStart = dragStart;
	}

	public Container getParentPanel() {
		return parentPanel;
	}

	public void setParentPanel(Container parentPanel) {
		this.parentPanel = parentPanel;
	}

	protected LabContainer lastConnectedContainer;
	protected String lastConnectedConnectorKey;
	protected int drawnTimes;
	protected Component connectorComponent;
	protected Point dragEnd = new Point();
	protected Point dragStart = new Point();
	private Container parentPanel;
	private boolean waveAll;
	private Map<String, Integer> offsetMap;
	private boolean threadRunning;
	
	abstract protected List<List<LabContainer>> getColumns();
	
	public WirePainter(Container parentPanel) {
		this.parentPanel = parentPanel;
		offsetMap = new HashMap<String, Integer>();
	}
	
	private void drawWire(LabContainer container, Graphics2D g) {
		for (String key : container.getInConnectorKeySet()) {
			Connector c = container.getInConnector(key);

			LabContainer from = c.getParent();
			if (from == null) {
				continue;
			}
			String fromKey = c.getKey();
			Point startPoint = getPosition(from, fromKey);
			
			LabContainer to = container;
			String toKey = key;
			Point endPoint = getPosition(to, toKey);
			
			if ( (to.equals(this.lastConnectedContainer) && toKey.equals(this.lastConnectedConnectorKey)) ||
					waveAll) {
				drawWireAfterMotion(g, startPoint, endPoint, from.getCalculatorName()+fromKey+toKey);
			}
			else {
				drawWireDragging(g, startPoint, endPoint);
			}
		}
	}

	private synchronized void drawWireAfterMotion(Graphics2D g, Point startPoint, Point endPoint, String key) {
//		System.out.println("drawWireAfterMotion" + drawnTimes);
		if (startPoint == null || endPoint == null) {
			return;
		}
		try {
			int offset = 0;
			if (waveAll) {
				if (offsetMap.containsKey(key)) {
					offset = offsetMap.get(key);
				}
				else {
					offsetMap.put(key, (int)(Math.random()*5));
				}
/*				if (drawnTimes == 0) {
					offsetMap.put(key, (int)(Math.random()*5));
				}
				else {
					try {
						offset = offsetMap.get(key);
					}
					catch (Exception e) {
						//System.out.println(key + ":"+e.toString());
					}
				}*/
			}
			int xoffset = - parentPanel.getLocationOnScreen().x;
			int yoffset = - parentPanel.getLocationOnScreen().y;
			drawWavingWire(
					startPoint.x + xoffset , startPoint.y + yoffset, 
					endPoint.x + xoffset, endPoint.y + yoffset, g, drawnTimes, offset);
		}
		catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private void drawWireDragging(Graphics2D g, Point startPoint, Point endPoint) {
		drawWire(g, startPoint, endPoint, false);
	}
	
	private void drawWire(Graphics2D g, Point startPoint, Point endPoint, boolean afterMotion) {
		int xoffset = - parentPanel.getLocationOnScreen().x;//-5 - parentPanel.getLocationOnScreen().x;
		int yoffset = - parentPanel.getLocationOnScreen().y;//-30 - parentPanel.getLocationOnScreen().y;
		if (startPoint != null) {
			//System.out.println("startPoint.y="+startPoint.y);
			drawRoundWire(
					startPoint.x + xoffset , startPoint.y + yoffset, 
					endPoint.x + xoffset, endPoint.y + yoffset, g, Color.BLACK);
		}
	}
	
	private Point getPosition(LabContainer container, String key) {
		if (container != null) {
			return container.getUI().getPoint(key);
		}

		return null;
	}
		
	private boolean isNewConnection() {
		return connectorComponent != null;
	}
	
	public synchronized void draw(Graphics2D g) {
//		System.out.println("draw");
		List<List<LabContainer>> columns = getColumns();
		for (List<LabContainer> panel : columns) {
			for (LabContainer c : panel) {
				CalculatorUiPanel p = c.getUI();//(CalculatorUiPanel)c;
				drawWire(p.getContainer(), g);
			}
		}
		if (isConnectorDragging()) { // Now dragging
			if (isNewConnection()) {
				Point startPoint = new Point();
				startPoint.setLocation(
						connectorComponent.getLocationOnScreen().getX() + connectorComponent.getWidth()/2, 
						connectorComponent.getLocationOnScreen().getY() + connectorComponent.getHeight()/2);
				drawWireDragging(g, startPoint, dragEnd);
			}
			else { // Change connection
				drawWireDragging(g, dragStart, dragEnd);
			}
		}
	}
	
	public void startWavingDrawAll() {
//		System.out.println("startWavingDrawAll");
		offsetMap.clear();
		startWavingDraw(true);
	}
	
	public void startWavingDraw() {
		startWavingDraw(false);
	}
	private void startWavingDraw(boolean all) {
		waveAll = all;
		new Thread() {
			@Override
			public void run() {
				while(true) {
					
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					parentPanel.repaint();
					if (drawnTimes++ > 15) {
						break;
					}
				}
				drawnTimes = 0;
				lastConnectedContainer = null;
				waveAll = false;
			}
			
		}.start();
	}
	
	private void drawRoundWire(int x1, int y1, int x2, int y2, Graphics2D g, Color color) {
		drawRoundWire(x1, y1, x2, y2, 
				(int)(((double)x1 + (double)x2)/2.0), 
				(int)(Math.max(y1, y2) + 5.0),
				g, color);
	}

	private void drawRoundWire(final int x1, final int y1, int x2, int y2, int x3, int y3, final Graphics2D g, Color color) {
		g.setStroke(new BasicStroke(2));
		double[] x = new double[3];
		double[] y = new double[3];
		x[0] = (double)x1;
		x[1] = (double)x3;
		x[2] = (double)x2;
		
		if (Math.abs(x[0] - x[1]) <= 0 || Math.abs(x[2] - x[1]) <= 0) {
			g.drawLine(x1, y1, x2, y2);
			drawPoint(x1, y1, g);
			drawPoint(x2, y2, g);
			return;
		}
		y[0] = y1;
		y[1] = y3;
		y[2] = y2;
		double a = ((y[0] - y[1]) * (x[0] - x[2]) - (y[0] - y[2]) * (x[0] - x[1])) / ((x[0] - x[1]) * (x[0] - x[2]) * (x[1] - x[2]));
		double b = (y[0] - y[1]) / (x[0] - x[1]) - a * (x[0] + x[1]);
		double c = y[0] - a * x[0] * x[0] - b * x[0];
		
		int xx[] = new int[Math.abs(x2 - x1)+1];
		int yy[] = new int[xx.length];
		int xstart;
		if (x1 < x2) {
			xstart = x1;
		}
		else {
			xstart = x2;
		}
		
		// Main Line
		for (int i = 0; i < xx.length; i++) {
			xx[i] = xstart + i;
			yy[i] = (int)(a * Math.pow((double)xx[i], 2.0) + b * (double)xx[i] + c);
			//System.out.println(xx[i] + "," + yy[i]);
		}
		
		// Shadow
		int[] xs = new int[xx.length];
		int[] ys = new int[xx.length];
		for (int i = 0; i < xx.length; i++) {
			xs[i] = xstart + i;
			ys[i] = (int)(a * Math.pow((double)xx[i], 2.0) + b * (double)xx[i] + c) + 2;
			//System.out.println(xx[i] + "," + yy[i]);
		}
		
		g.setColor(Color.gray);
		g.drawPolyline(xs, ys, xs.length);
		
		g.setColor(color);
		g.drawPolyline(xx, yy, xx.length);
		
		drawPoint(x1, y1, g);
		drawPoint(x2, y2, g);
		
	}

	private void drawPoint(int x1, int y1, Graphics2D g) {
		g.drawOval(x1-3, y1-3, 6, 6);
	}

	private void drawWavingWire(int x1, int y1, int x2, int y2, Graphics2D g,
			int count, int offset) {
		double coef;
		if (waveAll) {
			coef = 3;
		}
		else {
			coef = 5;
		}
		int x3 = (x1 + x2) / 2;
		int y3 = Math.max(y1,y2)+5 + (int)(/*Math.abs(y1-y2)*/30 * Math.exp(-1.0 * (double)count/coef) * Math.sin((double)(count+offset)/2.0));
		drawRoundWire(x1, y1, x2, y2, x3, y3, g, Color.black);
	}
}
